use axum::{routing::{get, post}, Router};
use serde::Deserialize;

use crate::{
    error::AppErr,
    server::{
        client::message::Message, req::{
            device::DeviceInfo, handler::{save_device, MessageHandler}, tick::Tick
        }, res::Void, Cbor
    },
    store::device::{self, login, query_all, update_with_client, update_with_web, TbDevice},
};

#[derive(Debug, Deserialize)]
struct DeviceLoginReq {
    mac_addr: String,
    version: String,
}

async fn on_login(req: Cbor<DeviceLoginReq>) -> Result<Cbor<u64>, AppErr> {
    let id = login(&req.mac_addr, &req.version).await?;
    save_device(id);
    Ok(Cbor(id))
}

// ack pong resp
async fn on_send_message(handler: MessageHandler, msg: Message) -> Result<Void, AppErr> {
    handler.post(msg)?;
    Ok(Void)
}

// ping notify notify_ack req
async fn on_poll_message(_: Tick, handler: MessageHandler) -> Result<Message, AppErr> {
    handler.poll().await
}

async fn on_ping(h: MessageHandler) -> Result<Void, AppErr> {
    h.ping().await?;
    Ok(Void)
}

#[derive(Debug, Deserialize)]
struct UpdateClientReq {
    ipc_version: String,
    phone_number: Option<String>,
    mcu_version: String,
}

async fn on_update_with_client(info: DeviceInfo, req: Cbor<UpdateClientReq>) -> Result<Void, AppErr> {
    update_with_client(
        info.id,
        &req.ipc_version,
        req.phone_number.as_deref(),
        &req.mcu_version,
    )
    .await?;
    Ok(Void)
}

#[derive(Debug, Deserialize)]
struct UpdateWebReq {
    name: Option<String>,
    language: Option<String>,
    location: Option<String>,
}

async fn on_update_with_web(info: DeviceInfo, req: Cbor<UpdateWebReq>) -> Result<Void, AppErr> {
    update_with_web(
        info.id,
        req.name.as_deref(),
        req.language.as_deref(),
        req.location.as_deref(),
    )
    .await?;
    Ok(Void)
}

async fn on_query_all() -> Result<Cbor<Box<[TbDevice]>>, AppErr> {
    let all = query_all().await?;
    Ok(Cbor(all))
}

async fn on_get(info: DeviceInfo) -> Result<Cbor<TbDevice>, AppErr> {
    let device = device::get(info.id).await?;
    Ok(Cbor(device))
}

async fn on_delete(info: DeviceInfo) -> Result<(), AppErr> {
    device::delete(info.id).await?;
    Ok(())
}

pub fn register() -> Router {
    Router::new()
        .route("/login", post(on_login))
        .route("/send_message", post(on_send_message))
        .route("/poll_message", get(on_poll_message))
        .route("/ping", post(on_ping))
        .route("/update_with_client", post(on_update_with_client))
        .route("/update_with_web", post(on_update_with_web))
        .route("/query_all", get(on_query_all))
        .route("/get", get(on_get))
        .route("/delete", post(on_delete))
}
